home *** CD-ROM | disk | FTP | other *** search
/ CU Amiga Super CD-ROM 21 / CU Amiga Magazine's Super CD-ROM 21 (1998)(EMAP Images)(GB)[!][issue 1998-04].iso / CUCD / Utilities / PGP / source / pgp50i-b8a / tools / munge.c < prev    next >
Encoding:
C/C++ Source or Header  |  1998-01-04  |  11.3 KB  |  472 lines

  1. /*
  2.  * munge.c -- Program to convert a text file into "munged" form,
  3.  *            suitable for reconstruction from printed form.  Tabs are
  4.  *            made visible and checksums are added to each line and each
  5.  *            page to protect against transcription errors.
  6.  *
  7.  * Copyright (C) 1997 Pretty Good Privacy, Inc.
  8.  *
  9.  * Designed by Colin Plumb, Mark H. Weaver, and Philip R. Zimmermann
  10.  * Written by Mark H. Weaver
  11.  *
  12.  * $Id: munge.c,v 1.18 1997/07/09 15:07:49 colin Exp $
  13.  */
  14.  
  15. #include <stdio.h>
  16. #include <errno.h>
  17. #include <string.h>
  18. #include <ctype.h>
  19.  
  20. #include "crc.h"
  21.  
  22. /*
  23.  * The file is divided into pages, and the format of each page is
  24.  *
  25. --f414 000b2dc79af40010002 Page 1 of munge.c
  26.  
  27.  bc38e5 /*
  28.  40a838  * munge.c -- Program to convert a text file into munged form
  29.  647222  *
  30.  193f28  * Copyright (C) 1997 Pretty Good Privacy, Inc.
  31.  827222  *
  32.  699025  * Designed by Colin Plumb, Mark H. Weaver, and Philip R. Zimmermann
  33.  0d050c  * Written by Mark H. Weaver
  34.  *
  35.  * Where the first 2 columns are the high 8 bits (in hex) of a running
  36.  * CRC-32 of the page (the string "--", unlikely to be confused with
  37.  * any digits, indicates a page header line) and the next 4 columns
  38.  * are a CRC-16 of the rest of the line.  Then a space (not counted in
  39.  * the CRC), and the line of text.  Tabs are printed as the currency
  40.  * symbol (ISO Latin 1 character 164) followed by the appropriate number
  41.  * of spaces, and any form feeds are printed as a yen symbol (Latin 1 165).
  42.  * The CRC is computed on the transformed line, including the trailing
  43.  * newline.  No trailing whitespace is permitted.
  44.  *
  45.  * The header line contains a (hex) number of the form 0ffcccccccctpppnnnn,
  46.  * where the digit 0 is a version number, ff are flags, ccccccc is the CRC-32
  47.  * of the page, t is the tab size (usually 4 or 8; 0 for binary files that
  48.  * are sent in radix-64), ppp is the product number (usually 1, different
  49.  * for different books), and nnnn is the file number (sequential from 1).
  50.  *
  51.  * This is followed by " Page %u of " and the file name.
  52.  */
  53.  
  54. typedef struct MungeState
  55. {
  56.     int                binaryMode, tabWidth;
  57.     long            origLineNumber;
  58.     long            productNumber, fileNumber, pageNumber, lineNumber;
  59.     unsigned long    fileOffset;
  60.     word32            runningCRC;
  61.     char const *    fileName;
  62.     char const *    fileNameTail;
  63.     char *            pageBuffer;    /* Buffer large enough to hold one page */
  64.     char *            pagePos;    /* Current position in pageBuffer */
  65.     word16            hdrFlags;
  66.     FILE *            file;
  67.     FILE *            out;
  68. } MungeState;
  69.  
  70. void ChecksumLine(char const *line, size_t length,
  71.                char *prefix, word32 *runningCRC)
  72. {
  73.     word16    lineCRC;
  74.     byte    runCRCPart = 0;
  75.  
  76.     lineCRC = CalculateCRC16(0, (byte const *)line, length);
  77.     if (runningCRC != NULL)
  78.     {
  79.         *runningCRC = CalculateCRC32(*runningCRC, (byte const *)line, length);
  80.         runCRCPart = (*runningCRC >> 24);
  81.     }
  82.     sprintf(prefix, (FMT8 FMT16), runCRCPart, lineCRC);
  83.     prefix[6] = ' ';    /* Write a space over the null byte */
  84. }
  85.  
  86. /* Returns 1 for convenience */
  87. int PrintFileError(MungeState *state, char const *message)
  88. {
  89.     fprintf(stderr, "%s in %s %s %lu\n", message, state->fileName,
  90.             state->binaryMode ? "offset" : "line",
  91.             state->binaryMode ? state->fileOffset : state->origLineNumber);
  92.     return 1;
  93. }
  94.  
  95. int MungeLine(MungeState *state, char *buffer, int length,
  96.               char *line, int *bufferUsed)
  97. {
  98.     int        i, j;
  99.     char    ch;
  100.  
  101.     if (length < 1 || buffer[length - 1] != '\n')
  102.     {
  103.         buffer[length++] = '\n';
  104.         buffer[length] = '\0';
  105.     }
  106. /*    return PrintFileError(state, "ERROR: Missing newline at end of file"); */
  107.  
  108.     i = 0;
  109.     j = 0;
  110.     for (i = 0; i < length && j <= LINE_LENGTH; i++)
  111.     {
  112.         ch = buffer[i];
  113.         if (ch == '\t')
  114.         {
  115.             line[j++] = TAB_CHAR;
  116.             if (state->tabWidth < 1)
  117.                 return PrintFileError(state,
  118.                                       "ERROR: Tab found in radix64 stream");
  119.             else
  120.                 while (j % state->tabWidth && j <= LINE_LENGTH)
  121.                     line[j++] = TAB_PAD_CHAR;
  122.         }
  123.         else if (ch == '\n')
  124.         {
  125.             if (++i < length)
  126.                 return PrintFileError(state,
  127.                                 "UNEXPECTED ERROR: fgets read past newline!?");
  128.             break;
  129.         }
  130.         else if (ch == '\f')
  131.         {
  132.             i++;
  133.             line[j++] = FORMFEED_CHAR;
  134.             break;
  135.         }
  136.         else if (ch >= ' ' && ch <= '~')
  137.             line[j++] = ch;
  138.         else
  139.             return PrintFileError(state, "ERROR: Non-ASCII char");
  140.     }
  141.     /* Strip trailing spaces */
  142.     while (j > 0 && isspace((unsigned char)line[j - 1]))
  143.         j--;
  144.  
  145.     if (j > LINE_LENGTH)
  146.         return PrintFileError(state, "ERROR: Line too long");
  147.  
  148.     /* Add trailing newline and NULL */
  149.     line[j++] = '\n';
  150.     line[j++] = '\0';
  151.  
  152.     /* Return number of chars used from buffer */
  153.     *bufferUsed = i;
  154.  
  155.     return 0;
  156. }
  157.  
  158. static void
  159. Encode3(byte const src[3], char dest[4])
  160. {
  161.     dest[0] = radix64Digits[                     (src[0]>>2 & 0x3f)];
  162.     dest[1] = radix64Digits[(src[0]<<4 & 0x30) | (src[1]>>4 & 0x0f)];
  163.     dest[2] = radix64Digits[(src[1]<<2 & 0x3c) | (src[2]>>6 & 0x03)];
  164.     dest[3] = radix64Digits[(src[2]    & 0x3f)];
  165. }
  166.  
  167. static int
  168. EncodeLine(byte const *src, int srcLen, char *dest)
  169. {
  170.     char *    destp = dest;
  171.     byte    tempSrc[3];
  172.  
  173.     for (; srcLen >= 3; srcLen -= 3)
  174.     {
  175.         Encode3(src, destp);
  176.         src += 3; destp += 4;
  177.     }
  178.  
  179.     if (srcLen > 0)
  180.     {
  181.         memset(tempSrc, 0, sizeof(tempSrc));
  182.         memcpy(tempSrc, src, srcLen);
  183.         Encode3(src, destp);
  184.         src += 3; destp += 4; srcLen -= 3;
  185.         while (srcLen < 0)
  186.             destp[srcLen++] = '=';
  187.     }
  188.  
  189.     return destp - dest;
  190. }
  191.  
  192. static int
  193. MungeBinaryLine(MungeState *state, byte const *buffer, int length, char *line)
  194. {
  195.     char    binLine[128];
  196.     int        binLength;            /* Destination length */
  197.     int        used;
  198.  
  199.     binLength = EncodeLine(buffer, length, binLine);
  200.  
  201.     /* Append newline */
  202.     binLine[binLength++] = '\n';
  203.     binLine[binLength] = '\0';
  204.  
  205.     return MungeLine(state, binLine, binLength, line, &used);
  206. }
  207.  
  208. int MaybePageBreak(MungeState *state)
  209. {
  210.     if (state->lineNumber >= LINES_PER_PAGE)
  211.     {
  212.         char    line[512];
  213.         char *    lineData = line + PREFIX_LENGTH;
  214.  
  215.         sprintf(lineData, "%01x%02x%08lx%01x%03lx%04lx Page %ld of %s\n",
  216.                 0,                        /* 1: Format version 0 */
  217.                 state->hdrFlags,        /* 2: Flags */
  218.                 state->runningCRC,        /* 8: Running CRC32 */
  219.                 state->tabWidth,        /* 1: Tab width (0 means radix64) */
  220.                 state->productNumber,    /* 3: Product number (0 - 4095) */
  221.                 state->fileNumber,        /* 4: File number (0 - 65535) */
  222.                 state->pageNumber + 1,
  223.                 state->fileNameTail);
  224.  
  225.         if (strlen(lineData) > LINE_LENGTH + 1)
  226.         {
  227.             PrintFileError(state, "ERROR: Header line too long");
  228.             fprintf(stderr, "> %s", lineData);
  229.             return -1;
  230.         }
  231.  
  232.         /* Compute checksums and prefix them to line */
  233.         ChecksumLine(lineData, strlen(lineData), line, NULL);
  234.  
  235.         fprintf(state->out, "--%s\n%s", line + 2, state->pageBuffer);
  236.  
  237.         state->pageNumber++;
  238.         state->lineNumber = 0;
  239.         state->runningCRC = 0;
  240.         state->pagePos = state->pageBuffer;        /* Clear page buffer */
  241.     }
  242.     return 0;
  243. }
  244.  
  245. /*
  246.  * Search for Emacs "tab-width: " maker in file.
  247.  * Emacs is stricter about the format, but this will do.
  248.  */
  249. int FindTabWidth(MungeState *state)
  250. {
  251.     char const * const    tabWidthMarker = " tab-width: ";
  252.     char                buffer[512];
  253.     char *                p;
  254.     int                    length;
  255.     int                    tabWidth = 0;
  256.  
  257.     fseek(state->file, -(sizeof(buffer) - 1), SEEK_END);
  258.     length = fread(buffer, 1, sizeof(buffer) - 1, state->file);
  259.     buffer[length] = '\0';
  260.     p = strstr(buffer, tabWidthMarker);
  261.     if (p != NULL)
  262.     {
  263.         p += strlen(tabWidthMarker);
  264.         while (*p != '\0' && *p != '\n' && isspace(*p))
  265.             p++;
  266.         tabWidth = strtol(p, &p, 10);
  267.         while (*p != '\0' && *p != '\n' && isspace(*p))
  268.             p++;
  269.         if (*p != '\n' || tabWidth < 2)
  270.             tabWidth = 0;
  271.         else if (tabWidth > 16)
  272.             fprintf(stderr, "WARNING: Weird tab-width (%d), %s\n",
  273.                             tabWidth, state->fileName);
  274.     }
  275.     return tabWidth;
  276. }
  277.  
  278. /*
  279.  * Open the given source file and send the munged output to the
  280.  * FILE *, with the given options.
  281.  */
  282. int MungeFile(char const *fileName, FILE *out, int binaryMode,
  283.               int defaultTabWidth, long productNumber, long fileNumber)
  284. {
  285.     MungeState *    state;
  286.     int                length, used;
  287.     char            line[PREFIX_LENGTH + LINE_LENGTH + 10];
  288.     char *            lineData = line + PREFIX_LENGTH;
  289.     char            buffer[128];
  290.     int                result = 0;
  291.  
  292.     state = (MungeState *)calloc(1, sizeof(*state));
  293.     state->origLineNumber = 0;
  294.     state->fileName = fileName;
  295.     state->runningCRC = 0;
  296.     state->productNumber = productNumber;
  297.     state->fileNumber = fileNumber;
  298.     state->pageNumber = 0;
  299.     state->lineNumber = 0;
  300.     state->fileOffset = 0;
  301.     state->binaryMode = binaryMode;
  302.     state->pageBuffer = malloc(PAGE_BUFFER_SIZE);
  303.     state->pageBuffer[0] = '\0';
  304.     state->pagePos = state->pageBuffer;
  305.     state->hdrFlags = 0;
  306.     state->out = out;
  307.  
  308.     state->fileNameTail = strrchr(state->fileName, '/');
  309.     if (state->fileNameTail == NULL)
  310.         state->fileNameTail = state->fileName;
  311.     else
  312.         state->fileNameTail++;
  313.  
  314.     state->file = fopen(state->fileName, binaryMode ? "rb" : "r");
  315.     if (state->file == NULL)
  316.     {
  317.         result = errno;
  318.         goto error;
  319.     }
  320.     
  321.     if (state->binaryMode)
  322.     {
  323.         state->tabWidth = 0;
  324.     }
  325.     else
  326.     {
  327.         state->tabWidth = FindTabWidth(state);
  328.         if (state->tabWidth == 0)
  329.             state->tabWidth = defaultTabWidth;
  330.         rewind(state->file);
  331.     }
  332.  
  333.     while (!feof(state->file))
  334.     {
  335.         state->origLineNumber++;
  336.  
  337.         if (state->binaryMode)
  338.         {
  339.             length = fread(buffer, 1, BYTES_PER_LINE, state->file);
  340.             if (length < 1)
  341.             {
  342.                 if (feof(state->file))
  343.                     break;
  344.                 goto fileError;
  345.             }
  346.             if ((result = MaybePageBreak(state)))
  347.                 goto error;
  348.             if ((result = MungeBinaryLine(state, buffer, length, lineData)))
  349.                 goto error;
  350.             state->fileOffset += length;
  351.         }
  352.         else
  353.         {
  354.             if (fgets(buffer, sizeof(buffer), state->file) == NULL)
  355.             {
  356.                 if (feof(state->file))
  357.                     break;
  358.                 goto fileError;
  359.             }
  360.             length = strlen(buffer);
  361.             if ((result = MaybePageBreak(state)))
  362.                 goto error;
  363.             if ((result = MungeLine(state, buffer, length, lineData, &used)))
  364.                 goto error;
  365.  
  366.             if (used < length)
  367.                 if (fseek(state->file, used - length, SEEK_CUR))
  368.                     goto fileError;
  369.         }
  370.  
  371.         /* Compute checksums and prefix them to the line */
  372.         ChecksumLine(lineData, strlen(lineData), line, &state->runningCRC);
  373.  
  374.         strcpy(state->pagePos, line);
  375.         length = strlen(state->pagePos);
  376.         /* Suppress trailing whitespace on blank lines */
  377.         if (length == PREFIX_LENGTH+1 && state->pagePos[length-1] == '\n') {
  378.             state->pagePos[--length-1] = '\n';
  379.             state->pagePos[length] = '\0';
  380.         }
  381.         state->pagePos += length;
  382.  
  383.         state->lineNumber++;
  384.     }
  385.  
  386.     if (state->lineNumber > 0)
  387.     {
  388.         /* Force a final page break */
  389.         state->lineNumber = LINES_PER_PAGE;
  390.         state->hdrFlags |= HDR_FLAG_LASTPAGE;
  391.         if ((result = MaybePageBreak(state)))
  392.             goto error;
  393.     }
  394.  
  395.     result = 0;
  396.     goto done;
  397.  
  398. fileError:
  399.     result = ferror(state->file);
  400.  
  401. error:
  402. done:
  403.     if (state != NULL)
  404.     {
  405.         if (state->file != NULL)
  406.             fclose(state->file);
  407.         free(state);
  408.     }
  409.     return result;
  410. }
  411.  
  412. int main(int argc, char *argv[])
  413. {
  414.     int        result = 0;
  415.     int        i, j;
  416.     int        defaultTabWidth = 4;
  417.     int        binaryMode = 0;
  418.  
  419.     InitCRC();
  420.  
  421.     for (i = 1; i < argc && argv[i][0] == '-'; i++)
  422.     {
  423.         if (0 == strcmp(argv[i], "--"))
  424.         {
  425.             i++;
  426.             break;
  427.         }
  428.         for (j = 1; argv[i][j] != '\0'; j++)
  429.         {
  430.             if (isdigit(argv[i][j]))
  431.             {
  432.                 defaultTabWidth = argv[i][j] - '0';
  433.                 if (defaultTabWidth < 2 || defaultTabWidth > 9)
  434.                     fprintf(stderr, "WARNING: Weird default tab-width (%d)\n",
  435.                                     defaultTabWidth);
  436.             }
  437.             else if (argv[i][j] == 'b')
  438.             {
  439.                 binaryMode = 1;
  440.             }
  441.             else
  442.             {
  443.                 fprintf(stderr, "ERROR: Unrecognized option -%c\n", argv[i][j]);
  444.                 exit(1);
  445.             }
  446.         }
  447.     }
  448.  
  449.     for (; i < argc; i++)
  450.     {
  451.         if ((result = MungeFile(argv[i], stdout, binaryMode,
  452.                                 defaultTabWidth, 1, argc)) != 0)
  453.         {
  454.             /* If result > 0, message should have already been printed */
  455.             if (result < 0)
  456.                 fprintf(stderr, "ERROR: %s\n", strerror(result));
  457.             exit(1);
  458.         }
  459.     }
  460.     
  461.     return 0;
  462. }
  463.  
  464. /*
  465.  * Local Variables:
  466.  * tab-width: 4
  467.  * End:
  468.  * vi: ts=4 sw=4
  469.  * vim: si
  470.  */
  471.  
  472.